"""
迭代器 和 生成器 介绍

迭代是数据处理的基石。扫描内存中放不下的数据集时，我们要找到一种惰性获取数据项的方式，即按需一次获取一个数据项。这就是迭代器模式。

迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问，直到所有的元素被访问完结束。迭代器只能往前不会后退。迭代器有两个基本的方法：
iter() 和 next()。

"""

# 1. 判断某个类型是否可迭代可通过内置函数 hasattr 和 迭代属性名 '__iter__'来判断
b = hasattr(list, '__iter__')
print(b)
h = hasattr(int, '__iter__')
print(h)

# 2. 列表转迭代器
l = [1, 2, 3, 4]
# 创建迭代器对象
i = iter(l)
# 访问 l，列表元素值打印在了控制台，说明已在内存中
print(l)
print(type(l))
# 访问 i ，并没有在控制台打印出列表的元素值，说明还没有到内存中
print(i)
print(type(i))
# 可以通过 迭代器对象中__next__ 方法将元素依次读取到内存中去
print(i.__next__())
print(i.__next__())
print(i.__next__())
# 也可直接通过 Python 的 内置函数 next 调用
print(next(i))
# 因为 迭代器 i 中 元素个数只有四个，所以只能迭代操作四次。如果如下再调用迭代操作，将会抛出异常
# print(i.__next__())

# 3. 通过 for 循环获取迭代器内容
# 因为 i 已经被迭代调用完了，所以需要重新声明一个迭代器对象
iterator = iter(l)
for i in iterator:
    print(i)

# 4. 内置的迭代器模块 itertools
import itertools

# 创建一个从 5 开始的无限大的迭代器。注意：此时所有的数字没在内存中，不占用内存
count = itertools.count(9)
print(next(count))
print(next(count))
print(next(count))
print(next(count))
print(next(count))

# 5. 生成器。在 Python 中使用了 yield 的函数被称为生成器（generator）。跟普通函数不同的是，生成器是一个返回迭代器的函数，只能用于迭代操作，更简
# 单点理解生成器就是一个迭代器。在调用生成器运行的过程中，每次遇到 yield 时函数会暂停并保存当前所有的运行信息，返回 yield 的值, 并在下一次执行
# next() 方法时从当前位置继续运行。调用一个生成器函数，返回的是一个迭代器对象。

# 迭代器用于从集合中取出元素；而生成器用于“凭空”生成元素。
# py3 中，内置的 range 函数也返回一个类似生成器的对象，而以前则返回完整的列表。如果一定要让 range 函数返回列表，那么必须明确指明，例如，list(range(100))


def fibonacci(n):

    a, bb, counter = 0, 1, 0
    while True:
        if counter > n:
            return
        yield a
        a, bb = bb, a + bb
        counter += 1


# f 是一个迭代器，由生成器返回生成
f = fibonacci(10)

while True:
    try:
        print(next(f), end=" ")
    except StopIteration:
        break



